/**
 * This file is Copyright © 2008 Software Craftsmen Limited. All Rights Reserved.
 */
package com.softwarecraftsmen.orogeny.actions;

import com.softwarecraftsmen.orogeny.buildEnvironments.BuildEnvironment;
import com.softwarecraftsmen.orogeny.buildLogs.BuildLog;
import com.softwarecraftsmen.orogeny.execution.SuccessOrFailure;
import com.softwarecraftsmen.orogeny.filing.*;
import com.softwarecraftsmen.orogeny.filing.findFileFilters.AbstractFindFilesFilter;
import com.softwarecraftsmen.orogeny.filing.findFileFilters.FindFilesFilter;
import org.jetbrains.annotations.NotNull;

import java.io.IOException;

import static com.softwarecraftsmen.orogeny.actions.MakeDirectoryAction.makeDirectory;
import static com.softwarecraftsmen.orogeny.buildLogs.Verbosity.AllDetails;
import static com.softwarecraftsmen.orogeny.buildLogs.Verbosity.Summary;
import static com.softwarecraftsmen.orogeny.execution.SuccessOrFailure.Failure;
import static com.softwarecraftsmen.orogeny.execution.SuccessOrFailure.Success;
import static com.softwarecraftsmen.orogeny.filing.AbsoluteDirectoriesAndFiles.absoluteDirectoriesAndFiles;
import static java.lang.String.format;
import static java.util.Locale.UK;

public class CopyFilesAction extends AbstractAction
{
	private final FindFilesFilter filesToHardLink;
	private final AbsolutePaths fromDirectoriesAndFiles;
	private final AbsoluteDirectory toDirectory;
	private final boolean flatten;

	public CopyFilesAction(final @NotNull FindFilesFilter filesToHardLink, final @NotNull AbsolutePaths fromDirectoriesAndFiles, final @NotNull AbsoluteDirectory toDirectory, final boolean flatten)
	{
		this.filesToHardLink = filesToHardLink;
		this.fromDirectoriesAndFiles = fromDirectoriesAndFiles;
		this.toDirectory = toDirectory;
		this.flatten = flatten;
	}

	@NotNull
	public String describeWhatWeDo()
	{
		return flatten ? "flatHardLinkFiles" : "hardLinkFiles";
	}

	@NotNull
	public SuccessOrFailure execute(final @NotNull BuildLog buildLog, final @NotNull BuildEnvironment buildEnvironment)
	{
		makeDirectory(toDirectory);
		if (flatten)
		{
			return hardLinkFlatten(buildLog, buildEnvironment);
		}
		else
		{
			return hardLinkFiles(buildLog, buildEnvironment);
		}
	}

	private SuccessOrFailure hardLinkFlatten(final @NotNull BuildLog buildLog, final @NotNull BuildEnvironment buildEnvironment)
	{
		buildLog.writeMessage(Summary, format(UK, "About to flat hardlink files %1$s from %2$s to %3$s", filesToHardLink, fromDirectoriesAndFiles, toDirectory));
		for (AbsoluteFile original : fromDirectoriesAndFiles.findFiles(filesToHardLink))
		{
			final AbsoluteFile copy = original.asIfIn(toDirectory);
			try
			{
				copyFile(buildLog, original, copy);
			}
			catch (IOException e)
			{
				return Failure(e);
			}
		}
		return Success;
	}

	private SuccessOrFailure hardLinkFiles(final @NotNull BuildLog buildLog, final @NotNull BuildEnvironment buildEnvironment)
	{
		buildLog.writeMessage(Summary, format(UK, "About to hardlink files %1$s from %2$s to %3$s", filesToHardLink, fromDirectoriesAndFiles, toDirectory));

		fromDirectoriesAndFiles.findFiles(new AbstractFindFilesFilter()
		{
			public boolean include(final @NotNull AbsoluteDirectory root, final @NotNull RelativeDirectory relativeDirectory, final @NotNull FileName fileName)
			{
				final AbsoluteFile original = relativeDirectory.makeAbsolute(root).file(fileName);
				final AbsoluteFile copy = original.toRelativeFile(root).makeAbsolute(toDirectory);

				final AbsoluteDirectory parentFolder = copy.parentDirectory();
				parentFolder.makeAllFoldersNecessary();

				if (filesToHardLink.exclude(root, relativeDirectory, fileName))
				{
					return false;
				}

				try
				{
					copyFile(buildLog, original, copy);
				}
				catch(IOException e)
				{
					throw new RuntimeException("Copying failed", e);
				}
				return false;
			}
		});
		return Success;
	}

	private void copyFile(final BuildLog buildLog, final AbsoluteFile original, final AbsoluteFile copy) throws IOException
	{
		buildLog.writeMessage(AllDetails, format(UK, "About to copy original file %1$s to %2$s", original, copy));
		original.hardLinkTo(copy);
	}

	@NotNull
	public static SpecifyFilesToCopy hardLinkFiles(final @NotNull FindFilesFilter filesToCopy)
	{
		return new SpecifyFilesToCopy(filesToCopy, false);
	}

	@NotNull
	public static SpecifyFilesToCopy flatHardLinkFiles(final @NotNull FindFilesFilter filesToCopy)
	{
		return new SpecifyFilesToCopy(filesToCopy, true);
	}

	public static final class SpecifyFilesToCopy
	{
		private final FindFilesFilter filesToCopy;
		private final boolean flatten;

		private SpecifyFilesToCopy(final @NotNull FindFilesFilter filesToCopy, final boolean flatten)
		{
			this.filesToCopy = filesToCopy;
			this.flatten = flatten;
		}

		@NotNull
		public SpecifyFrom from(final @NotNull AbsolutePath... fromDirectoriesAndFiles)
		{
			return new SpecifyFrom(absoluteDirectoriesAndFiles(fromDirectoriesAndFiles));
		}

		@NotNull
		public SpecifyFrom from(final @NotNull AbsolutePaths... fromDirectoriesAndFiles)
		{
			final AbsolutePaths actual = absoluteDirectoriesAndFiles(fromDirectoriesAndFiles);
			return new SpecifyFrom(actual);
		}

		public final class SpecifyFrom
		{
			private final AbsolutePaths fromDirectoriesAndFiles;

			private SpecifyFrom(final @NotNull AbsolutePaths fromDirectoriesAndFiles) {this.fromDirectoriesAndFiles = fromDirectoriesAndFiles;}

			@NotNull
			public CopyFilesAction to(final @NotNull AbsoluteDirectory toDirectory)
			{
				return new CopyFilesAction(filesToCopy, fromDirectoriesAndFiles, toDirectory, flatten);
			}
		}
	}
}
